//
// Created by 86136 on 2024/11/22.
//
#include <cstdlib>
#include "pch.h"
#include "ngx_mem_pool.h"

// 根据size进行内存开辟
void* ngx_mem_pool::ngx_create_pool(size_t size){
    ngx_pool_s *p;

    p = (ngx_pool_s *)malloc(size);
    if (p == nullptr) return nullptr;

    p->d.last = (u_char *) p + sizeof(ngx_pool_s);  // 指向可用内存的起始地址
    p->d.end = (u_char *) p + size;                 // 指向可用内存的末尾地址
    p->d.next = nullptr;                            // 指向下一个内存块，当前刚申请内存块，所以置空
    p->d.failed = 0;                                // 内存块是否开辟成功

    size = size - sizeof(ngx_pool_s);               // 能使用的空间 = 总空间 - 头信息
    // 指定的大小若大于一个页面就用一个页面，否则用指定的大小
    // max = min(size, 4095)，max指的是除开头信息以外的内存块的大小
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p;         // 指向可用于分配空间的内存块的起始地址
    p->large = nullptr;     // 小块内存直接在内存块开辟，大块内存在large指向的内存开辟
    p->cleanup = nullptr;
    pool = p;
    return p;
}

// 考虑内存字节对齐，从内存池申请size大小的内存
void *ngx_mem_pool::ngx_palloc(size_t size){
    if (size <= pool->max) {
        // 当前分配的空间小于max，小块内存的分配
        return ngx_palloc_small(size, 1);   // 考虑内存对齐
    }
    return ngx_palloc_large(size);
}

// 不考虑内存字节对齐，从内存池申请size大小的内存
void *ngx_mem_pool::ngx_pnalloc(size_t size){
    if (size <= pool->max) {
        // 当前分配的空间小于max，小块内存的分配
        return ngx_palloc_small(size, 0);   // 不考虑内存对齐
    }
    return ngx_palloc_large(size);
}

// 考虑内存字节对齐，从内存池申请size大小的内存并初始化为0
void *ngx_mem_pool::ngx_pcalloc(size_t size){
    void *p;
    p = ngx_palloc(size); // 考虑内存对齐
    if (p) ngx_memzero(p, size);   // 可以初始化内存为0
    return p;
}

// 小块内存分配
void *ngx_mem_pool::ngx_palloc_small(size_t size, ngx_uint_t align){
    u_char *m;
    ngx_pool_s *p;
    // 从第一个内存块的current指针指向的内存池进行分配
    p = pool->current;

    do {
        m = p->d.last;  // m指向可分配内存的起始地址

        if (align) {
            // 把m调整为NGX_ALIGNMENT整数倍
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }
        // 内存池分配内存的核心代码
        if ((size_t) (p->d.end - m) >= size) {
            // 若可分配空间 >= 申请的空间
            // 偏移d.last指针，记录空闲空间的首地址
            p->d.last = m + size;
            return m;
        }
        // 当前内存块的空闲空间不够分配，若有下一个内存块则转向下一个内存块
        // 若没有，p会被置空，退出while
        p = p->d.next;
    } while (p);

    // 内存块不够分配时，会执行ngx_palloc_block
    return ngx_palloc_block(size);
}

// 分配新的小块内存池
void *ngx_mem_pool::ngx_palloc_block(size_t size){
    u_char *m;
    size_t psize;
    ngx_pool_s *p, *newp;
    // 开辟与上一个内存块大小相同的内存块
    psize = (size_t) (pool->d.end - (u_char *) pool);

    // 将psize对齐为NGX_POOL_ALIGNMENT的整数倍后，向OS申请空间
    m = (u_char*)malloc(psize);
    if (m == nullptr) {
        return nullptr;
    }

    newp = (ngx_pool_s *) m;    // 指向新开辟内存块的起始地址

    newp->d.end = m + psize;    // 指向新开辟内存块的末尾地址
    newp->d.next = nullptr;		   // 下一块内存的地址为NULL
    newp->d.failed = 0;		   // 当前内存块分配空间失败的次数

    // 指向头信息的尾部，而max，current、chain等只在第一个内存块有
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    newp->d.last = m + size;                // last指向当前块空闲空间的起始地址

    // 由于每次都是从pool->current开始分配空间
    // 若执行到这里，除了new所属的这个内存块分配成功，前面的内存块全部分配失败
    for (p = pool->current; p->d.next != nullptr; p = p->d.next) {
        // 对所有的内存块的failed都++，直到该内存块分配失败的次数大于4了，就表示该内存块的剩余空间很小了，不能再分配空间了
        // 就修改current指针，下次从current开始分配空间，再次分配的时候可以不用遍历current前面的内存块
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    p->d.next = newp;   // 连接可分配空间的首个内存块 和 新开辟的内存块

    return m;
}

// 大块内存分配
void *ngx_mem_pool::ngx_palloc_large(size_t size){
    void *p;
    ngx_uint_t n;
    ngx_pool_large_s *large;

    p = malloc(size);
    if (p == nullptr) {
        return nullptr;
    }

    n = 0;
    // 并不是每次分配大块内存都重新在小块内存上分配头信息，会遍历存储大块内存头信息的链表，查找已经释放的大块内存
    // for循环遍历存储大块内存信息的链表
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == nullptr) {
            // 当大块内存被ngx_pfree时，alloc为NULL
            // 遍历链表，若大块内存的首地址为空，则把当前malloc的内存地址写入alloc
            large->alloc = p;
            return p;
        }
        // 遍历4次后，若还没有找到被释放过的大块内存对应的信息（只查看前4个内存块，由于是头插法，前4个内存块是不断改变的）
        // 为了提高效率，直接在小块内存中申请空间保存大块内存的信息
        if (n++ > 3) {
            break;
        }
    }
    // 通过指针偏移在小块内存池上分配存放大块内存头信息*next和*alloc的空间
    large = (ngx_pool_large_s *)ngx_palloc_small(sizeof(ngx_pool_large_s), 1);
    if (large == nullptr) {
        // 如果在小块内存上分配存储*next和*alloc空间失败，则无法记录大块内存头信息
        // 释放大块内存p
        free(p);
        return nullptr;
    }

    large->alloc = p;			   // alloc指向大块内存的首地址
    large->next = pool->large;	   // 这两句采用头插法，将新内存块的记录信息存放于以large为头结点的链表中
    pool->large = large;

    return p;
}

// 释放p指向的大块内存，找不到就什么都不做
void ngx_mem_pool::ngx_pfree(void *p){
    ngx_pool_large_s *l;
    for (l = pool->large; l; l = l->next) {
        // 遍历存储大块内存信息的链表，找到p对应的大块内存
        if (p == l->alloc) {
            // 释放大块内存，但不释放存储信息的内存空间
            free(l->alloc);  // free
            l->alloc = nullptr;     // alloc置空
            return ;
        }
    }
}

// 重置内存池
void ngx_mem_pool::ngx_reset_pool(){
    ngx_pool_s *p;
    ngx_pool_large_s *l;

    // 由于需要重置小块内存，而大块内存的控制信息在小块内存中保存
    // 所以需要先释放大块内存，在重置小块内存
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            // 释放大块内存前，需要调用预置的回调函数，释放占用的外部资源，然后再调用ngx_free释放内存池的大块内存
            free(l->alloc);
        }
    }

    // 遍历小块内存的链表，重置last、failed、current、chain、large等管理信息
    for (p = pool; p; p = p->d.next) {
        // 由于只有第一个内存块有除了ngx_pool_data_t以外的管理信息，别的内存块只有ngx_pool_data_t的信息
        // 不会出错，但是会浪费空间
        p->d.last = (u_char *) p + sizeof(ngx_pool_s);
        p->d.failed = 0;
    }
    /*
    正确写法如下：
    pool->d.last = (u_char *) pool + sizeof(ngx_pool_t);  // 第一个内存块的last指针偏移ngx_pool_t
    pool->d.failed = 0;
    for (p = pool->d.next; p; p = p->d.next) {
        // 由于只有第一个内存块有除了ngx_pool_data_t以外的管理信息，别的内存块只有ngx_pool_data_t的信息
        // 不会出错，但是会浪费空间
        p->d.last = (u_char *) p + sizeof(ngx_pool_data_t);  // 后续所有内存块的last指针偏移ngx_pool_data_t
        p->d.failed = 0;
    }
    */

    // current指向可用于分配内存的内存块
    pool->current = pool;
    pool->large = nullptr;
}

// 销毁内存池
void ngx_mem_pool::ngx_destroy_pool()
{
    ngx_pool_s          *p, *n;
    ngx_pool_large_s    *l;
    ngx_pool_cleanup_s  *c;

    // 遍历cleanup链表（存放的时释放前需要调用的函数），可释放外部占用的资源
    for (c = pool->cleanup; c; c = c->next) {
        if (c->handler) {
            c->handler(c->data);
        }
    }

    // 释放大块内存
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            free(l->alloc);
        }
    }

    // 释放小块内存池
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        free(p);

        if (n == nullptr) break;
    }
}

ngx_pool_cleanup_s* ngx_mem_pool::ngx_pool_cleanup_add(size_t size){
    ngx_pool_cleanup_s* c;

    // 开辟清理函数的结构体，实际上也是存放在内存池的小块内存
    c = (ngx_pool_cleanup_s*)ngx_palloc(sizeof(ngx_pool_cleanup_s));
    if (c == nullptr) {
        return nullptr;
    }

    if (size) {
        // 为c->data申请size的空间
        c->data = ngx_palloc(size);
        if (c->data == nullptr) {
            return nullptr;
        }
    } else {
        c->data = nullptr;
    }

    c->handler = nullptr;
    // 采用头插法，将当前结构体串在pool->cleanup后
    c->next = pool->cleanup;
    pool->cleanup = c;

    return c;
}
